package edu.luc.concordia;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

/**
 * Copyright 2008 Project Concordia
 * <p>
 * This file is part of RetailReturnSystem.
 * <p>
 * RetailReturnSystem is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * <p>
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * <p>
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>.
 * <hr>
 * SE project 
 * PriceScheme class
 * @author Yumin Li, Robbie McMahon 
 * @version 2.1
 */

/**
 * @author LostMyLaptop
 * 
 */
public class PriceScheme {
	public final static double CAN_NOT_BUY = Double.NaN;

	/*
	 * A list of strings representing the scheme calculations. Eg. Scheme
	 * 8100001 would be represented as ["50 2 1"]. Scheme 0100002 would be
	 * represented as ["1 49.99 N 2", "2 49.99 N 3", "3 49.99 Y 1"].
	 */
	private ArrayList<String> myScheme;
	private String myID; // Integer representation of the price scheme ID
	private String myDescription; // A string of the scheme's description. Eg.
	// Scheme 8100001 would have "Buy 1 get another 50% off".

	/*
	 * An integer designating the type of price scheme. Product, category, and
	 * system price schemes are one(1), two(2), and three(3) respectively. If
	 * the price scheme is also a member price scheme, then ten (10) is added.
	 * Eg. Scheme 8100001 would have a type of 2. Scheme 8200002 would have a
	 * type of 12. Scheme 0200004 would have a type 11.
	 */
	private int myType;

	/*
	 * Used to compare products
	 */
	private static final Comparator myProductComparator = new Comparator() {
		/**
		 * Compare method for Products. If the first product is cheaper than the
		 * second product, then the method returns -1. If both products are the
		 * same price and the first product has a lower product ID, then the
		 * method returns -1. Otherwise the method returns 1.
		 * 
		 * @param arg0
		 *            First product
		 * @param arg1
		 *            Second product
		 * @return -1 or 1 if the first product is cheaper, more expensive than
		 *         the second.
		 */
		@Override
		public int compare(Object arg0, Object arg1) {
			Product prod0 = (Product) arg0;
			Product prod1 = (Product) arg1;

			if (prod0.getCalculatedPrice() < prod1.getCalculatedPrice()) {
				return -1;
			} else if (prod0.getCalculatedPrice() > prod1.getCalculatedPrice()) {
				return 1;
			} else if (Integer.parseInt(prod0.getUID()) < Integer
					.parseInt(prod1.getUID())) {
				return -1;
			} else
				return -1;
		}

	};

	@Deprecated
	public PriceScheme(String rawScheme) {
		myScheme = new ArrayList<String>();
		String[] parsed = rawScheme.split(";");
		// System.err.println(parsed[0]);
		myScheme.add(parsed[0]);
	}

	/**
	 * Constructor for the PriceScheme class. Takes in an ArrayList containing
	 * the Price Scheme information.
	 * 
	 * @param rawScheme
	 *            The raw scheme input from the XML file such that the array
	 *            contains [id, description, calculation]. If the scheme does
	 *            not have a description, then the second index is an empty
	 *            string. Eg. Scheme 8100001 would be ["8100001",
	 *            "Buy 1 get another 50% off", "50 2 1"]. Scheme 0100002 would
	 *            be ["0100002 ", "", "1  49.99  N  2", "2  49.99  N  3",
	 *            "3  49.99  Y  1"].
	 */
	public PriceScheme(ArrayList<String> rawScheme) {
		// Inserted defect
		myScheme = new ArrayList<String>();

		// Parse out the price scheme ID
		myID = rawScheme.remove(0);
		// Get the price scheme description
		myDescription = rawScheme.remove(0);
		// Assign the rest of the array to the scheme
		myScheme.addAll(rawScheme);

		myType = 0;
		// Set myID
		int firstDigit = Integer.parseInt(myID.substring(0, 1));
		if (firstDigit >= 0 && firstDigit <= 5) {
			// product price scheme
			myType = 1;
		} else if (firstDigit == 8) {
			// category price scheme
			myType = 2;
		} else if (firstDigit == 9) {
			// system price scheme
			myType = 3;
		} else {
			// do nothing
		}

		int secondDigit = Integer.parseInt(myID.substring(1, 2));
		if (secondDigit == 2) {
			// member price scheme
			myType += 10;
		} else {
			// do nothing (regular price scheme)
		}
	}

	/**
	 * Copy constructor. Not necessary but good to have.
	 * 
	 * @param oldScheme
	 *            Old scheme to copy.
	 */
	public PriceScheme(PriceScheme oldScheme) {
		myID = oldScheme.getID();
		myDescription = oldScheme.getDescription();
		myScheme = oldScheme.getScheme();

		if (oldScheme.getType().equals("product")) {
			myType = 1;
		} else if (oldScheme.getType().equals("category")) {
			myType = 2;
		} else if (oldScheme.getType().equals("system")) {
			myType = 3;
		}
	}

	/**
	 * 
	 * @return The data for this scheme.
	 */
	public ArrayList<String> getScheme() {
		return (ArrayList<String>) myScheme.clone();
	}

	/**
	 * 
	 * @return The ID of this price scheme
	 */
	public String getID() {
		return myID;
	}

	/**
	 * 
	 * @return The description of this price scheme.
	 */
	public String getDescription() {
		return myDescription;
	}

	/**
	 * 
	 * @return The type of price scheme: product, category, or system.
	 */
	public String getType() {
		switch (myType % 10) {
		case 1:
			return "product";
		case 2:
			return "category";
		case 3:
			return "system";
		default:
			return null;
		}
	}

	/**
	 * 
	 * @return Whether this price scheme is for members only.
	 */
	public boolean isMember() {
		return myType > 10;
	}

	/**
	 * Calculates the product price of the nth item.
	 * 
	 * @param nth
	 *            Number of items scanned so far.
	 * @return Price of the nth item in this price scheme.
	 */
	public double calculateProductPrice(int nth) {
		// if this is not a product price scheme
		if (!this.getType().equals("product")) {
			return CAN_NOT_BUY;
		}

		int nextRow = 1; // next row to look at
		int numParsedRows = 0; // number of rows looked at so far
		double price = CAN_NOT_BUY; // price of current row
		boolean reachedLimit = false; // whether we have reached the limit
		while (!reachedLimit && numParsedRows < nth) {
			// parse a row and increment the count
			String[] parsedRow = myScheme.get(nextRow - 1).split(" ");
			numParsedRows++;

			if (numParsedRows == nth) {
				price = Double.parseDouble(parsedRow[1]);
			} else if (parsedRow[2].equals("Y")) {
				reachedLimit = true;
			} else {
				nextRow = Integer.parseInt(parsedRow[3]);
			}
		}

		return price;
	}

	/**
	 * Calculates the category price of all the products in productList
	 * 
	 * @param productList
	 *            List of products in the same category
	 * @return A list of products with all of their prices calculated
	 */
	public ArrayList<Product> calculateCategoryPrice(
			ArrayList<Product> productList) {
		// if this is not a category price scheme
		if (!this.getType().equals("category")) {
			return null;
		}

		String scheme = myScheme.get(0);
		// if myScheme is of the format "x y"
		if (scheme.split(" ").length == 2) {
			return this.ifOverPercentOff(productList);
			// else if myScheme is of the format "x y z"
		} else if (scheme.split(" ").length == 3) {
			return this.ifCheapestPercentOff(productList);
		} else {
			return null;
		}
	}

	/**
	 * Private helper method. Called when the category price scheme is in the
	 * format "x% off if over $y"
	 * 
	 * @param productList
	 *            List of products in the same category
	 * @return A list of products with all of their prices calculated
	 */
	private ArrayList<Product> ifOverPercentOff(ArrayList<Product> productList) {
		// parse myScheme
		String[] parsed = myScheme.get(0).split(" ");
		int percentOff = Integer.parseInt(parsed[0]);
		int minTotal = Integer.parseInt(parsed[1]);

		double subTotal = 0;

		for (Product prod : productList) {
			subTotal += prod.getCalculatedPrice();
		}

		if (subTotal >= minTotal) {
			double discount = (double) percentOff / 100.0;
			discount = 1.0 - discount;

			for (Product prod : productList) {
				double temp = prod.getCalculatedPrice();
				prod.setCalculatedPrice(temp * discount);
			}
		}

		return productList;
	}

	/**
	 * Private helper method. Called when the category price scheme is in the
	 * format "Buy y items and get x% off z cheapest"
	 * 
	 * @param productList
	 *            List of products in the same category
	 * @return A list of products with all of their prices calculated
	 */
	private ArrayList<Product> ifCheapestPercentOff(
			ArrayList<Product> productList) {

		String[] parsed = myScheme.get(0).split(" ");
		int percentOff = Integer.parseInt(parsed[0]);
		int minTotal = Integer.parseInt(parsed[1]);
		int numDiscount = Integer.parseInt(parsed[2]);

		// if productList length is less than the number of required items
		if (productList.size() < minTotal)
			return productList;

		Collections.sort(productList, myProductComparator);

		int itemsToDiscount = productList.size() / minTotal * numDiscount;

		// discount the appropriate number of cheapest products
		double discount = (double) percentOff / 100.0;
		discount = 1.0 - discount;
		for (int loop = 0; loop < itemsToDiscount; loop++) {
			double temp = productList.get(loop).getCalculatedPrice();
			productList.get(loop).setCalculatedPrice(temp * discount);
		}

		return productList;
	}

	/**
	 * Calculates system price.
	 * 
	 * @param total
	 *            Total receipt price
	 * @return Appropriately discounted price
	 */
	public double calculateSystemPrice(double total) {
		// if this is not a system price scheme
		if (!this.getType().equals("system")) {
			return CAN_NOT_BUY;
		}

		String[] parsed = myScheme.get(0).split(" ");
		int percentOff = Integer.parseInt(parsed[0]);
		int minTotal = Integer.parseInt(parsed[1]);

		if (total > minTotal) {
			double discount = (double) percentOff / 100.0;
			discount = 1.0 - discount;
			total = total * discount;
		}

		return total;
	}

	@Deprecated
	public double getPrice() {
		// Returns the price for this PriceScheme. In Cycle 1, there will only
		// be ProductPrices.
		String[] temp = myScheme.get(0).split(" ");
		return Double.parseDouble(temp[1]);
	}

}
